package org.elasticsearch;

import com.obs.services.internal.security.BasicSecurityKey;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;

import java.util.*;

import static org.elasticsearch.common.settings.Setting.*;

/**
 * OBS client configuration
 */
public class ObsClientSettings {
    final String endpoint;
    final String protocol;
    final String proxyHost;
    final Integer proxyPort;
    final String proxyUsername;
    final String proxyPassword;
    final String region;
    final String bucket;
    final BasicSecurityKey basicSecurityKey;
    final int maxRetries;

    private static final ByteSizeValue MIN_CHUNK_SIZE = new ByteSizeValue(1, ByteSizeUnit.MB);
    private static final ByteSizeValue MAX_CHUNK_SIZE = new ByteSizeValue(1, ByteSizeUnit.GB);
    public static final Setting<SecureString> ACCESS_KEY =
            new Setting<>("access_key", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> SECRET_ACCESS_KEY =
            new Setting<>("secret_key", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> SECURITY_TOKEN =
            new Setting<>("security_token", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> ENDPOINT =
            new Setting<>("endpoint", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> BUCKET =
            new Setting<>("bucket", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> BASE_PATH =
            new Setting<>("base_path", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> REGION =
            new Setting<>("region", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> PROXY_USERNAME_SETTING =
            new Setting<>("proxy.username", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    public static final Setting<SecureString> PROXY_PASSWORD_SETTING =
            new Setting<>("proxy.password", "", SecureString::new, Property.Filtered,
                    Property.Dynamic, Property.NodeScope);
    static final Setting<Protocol> PROTOCOL_SETTING = new Setting<>("protocol", "https", s -> Protocol.valueOf(s.toUpperCase(Locale.ROOT)), Property.NodeScope);

    /**
     * The host of a proxy to connect to obs through.
     */
    static final Setting<String> PROXY_HOST_SETTING = Setting.simpleString("proxy.host", Property.NodeScope);

    /**
     * The port of a proxy to connect to obs through.
     */
    static final Setting<Integer> PROXY_PORT_SETTING = Setting.intSetting("proxy.port", 80, 0, 1 << 16, Property.NodeScope);
    static final Setting<Integer> MAX_RETRIES_SETTING = Setting.intSetting("max_retries", 3, 1, 5, Property.NodeScope);
    public static final Setting<Boolean> COMPRESS =
            boolSetting("compress", false, Setting.Property.NodeScope, Setting.Property.Dynamic);
    public static final Setting<ByteSizeValue> CHUNK_SIZE =
            byteSizeSetting("chunk_size", MAX_CHUNK_SIZE, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE,
                    Setting.Property.NodeScope, Setting.Property.Dynamic);
    ObsClientSettings(BasicSecurityKey basicSecurityKey, String endpoint, String protocol, String proxyHost, Integer proxyPort, String proxyUsername, String proxyPassword, String region, String bucket, int maxRetries) {
        this.basicSecurityKey = basicSecurityKey;
        this.endpoint = endpoint;
        this.protocol = protocol;
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
        this.proxyUsername = proxyUsername;
        this.proxyPassword = proxyPassword;
        this.region = region;
        this.bucket = bucket;
        this.maxRetries = maxRetries;
    }

    static Map<String, ObsClientSettings> load(Settings settings) {
        final Set<String> clientNames = settings.keySet();
        final Map<String, ObsClientSettings> clients = new HashMap<>();
        for (final String clientName : clientNames) {
            clients.put(clientName, getClientSettings(settings, clientName));
        }
        if (clients.containsKey("default") == false) {
            clients.put("default", getClientSettings(settings, "default"));
        }
        return Collections.unmodifiableMap(clients);
    }

    static ObsClientSettings getClientSettings(final Settings settings, final String clientName) {
        try (SecureString proxyUsername = getConfigValue(settings, clientName, PROXY_USERNAME_SETTING);
             SecureString proxyPassword = getConfigValue(settings, clientName, PROXY_PASSWORD_SETTING)) {
            return new ObsClientSettings(
                    buildSecurity(settings, clientName),
                    getConfigValue(settings, clientName, ENDPOINT).toString(),
                    getConfigValue(settings, clientName, PROTOCOL_SETTING).toString(),
                    getConfigValue(settings, clientName, PROXY_HOST_SETTING),
                    getConfigValue(settings, clientName, PROXY_PORT_SETTING),
                    proxyUsername.toString(),
                    proxyPassword.toString(),
                    getConfigValue(settings, clientName, REGION).toString(),
                    getConfigValue(settings, clientName, BUCKET).toString(),
                    getConfigValue(settings, clientName, MAX_RETRIES_SETTING));
        }
    }

    private static BasicSecurityKey buildSecurity(Settings settings, String clientName) {
        try (SecureString accessKey = getConfigValue(settings, clientName, ACCESS_KEY);
             SecureString secretKey = getConfigValue(settings, clientName, SECRET_ACCESS_KEY);
             SecureString sessionToken = getConfigValue(settings, clientName, SECURITY_TOKEN)) {
            if (accessKey.length() != 0) {
                if (secretKey.length() != 0) {
                    if (sessionToken.length() != 0) {
                        return new BasicSecurityKey(accessKey.toString(), secretKey.toString(), sessionToken.toString());
                    } else {
                        return new BasicSecurityKey(accessKey.toString(), secretKey.toString());
                    }
                } else {
                    throw new IllegalArgumentException("Missing secret key for obs client [" + clientName + "]");
                }
            } else {
                if (secretKey.length() != 0) {
                    throw new IllegalArgumentException("Missing access key for obs client [" + clientName + "]");
                }
                if (sessionToken.length() != 0) {
                    throw new IllegalArgumentException("Missing access key and secret key for obs client [" + clientName + "]");
                }
                return null;
            }
        }
    }

    private static <T> T getConfigValue(Settings settings, String clientName,
                                        Setting<T> clientSetting) {
        final Setting<T> concreteSetting = clientSetting.getConcreteSetting(clientSetting.getKey());
        return concreteSetting.get(settings);
    }

}
